#include "d3d.h"
#include "d3d_particles.h"
#include "d3d_texture.h"
#include "d3d_mesh.h"
#include ".\shaders\d3d_shader.h"
#include ".\shaders\d3d_depthmapshader.h"
#include ".\shaders\d3d_particleshader.h"
#include ".\shaders\d3d_lightshader.h"
#include ".\shaders\d3d_screenshader.h"
#include "d3d_pointsprite.h"
#include "d3d_image.h"
#include "d3d_light.h"
#include "d3d_util.h"
#include "..\common\def.h"
#include <assert.h>


#include "..\engine\engine.h"
#include "..\input\dinput.h"


void CD3D9Device::AddRenderQue(D3DRenderable* const pMesh, const Matrix4f& transform)
{
#ifdef _DEBUG
	if(ISNULL(pMesh))
	{
		EE_LOGERROR("Invalid parameter"); 
		return; 
	}
#endif

	int renderFlags = pMesh->getRenderFlags(); 


	//This is taken care of directly
	if(pMesh == m_pSkyDome)
	{
		m_pSkyShader = pMesh->getShader();
		m_RenderSky = true;
		m_pSkyShader->AddRenderQue(pMesh, Matrix4f::identity());
		//if((renderFlags & RENDER_LIGHTING) && (renderFlags & RENDER_FLARE))
		//	m_pLightShader->AddRenderQue(pMesh, transform); 

	}
	else
	{
		
		float bloom = (pMesh->getRenderFlags() & RENDER_FLARE) ? 1.0f : 0.0f; 
		
		pMesh->getShader()->AddRenderQue(pMesh, transform); 
		if(pMesh->getRenderFlags() & RENDER_LIGHTING)
			m_pLightShader->AddRenderQue(pMesh, transform); 
		
		if(pMesh->getRenderFlags() & RENDER_CAST_SHADOW)
		{
			m_pDepthShader->AddRenderQue(pMesh, transform); 
		}

		if(!m_RenderReflections) {

			//TODO: this is murdering performance
			if(pMesh->getShader()->GetType().equals("D3DReflectShader")) {
				m_RenderReflections = true;
			}

		}
	}
}

void CD3D9Device::AddRenderQue(D3DRenderable* const pMesh, const Matrix4f& transform, D3DShader* const m_pShader)
{
#ifdef _DEBUG
	if(!pMesh || !m_pShader)
	{
		EE_LOGERROR("Invalid parameter(s)"); 
		return; 
	}
#endif
	
	//This is taken care of directly
	if(pMesh == m_pSkyDome) {

		m_pSkyShader = m_pShader;
		m_RenderSky = true;
		m_pSkyShader->AddRenderQue(pMesh, Matrix4f::identity());
	}
	else 
	{
		m_pShader->AddRenderQue(pMesh, transform); 

		if(pMesh->getRenderFlags() & (RENDER_LIGHTING | RENDER_CAST_SHADOW))
			m_pDepthShader->AddRenderQue(pMesh, transform); 

		if(pMesh->getRenderFlags() & RENDER_LIGHTING)
			m_pLightShader->AddRenderQue(pMesh, transform); 
	}
}

void CD3D9Device::RenderDepthMap(RenderTarget* pDepthBuffer, ViewOrrientation *pView)
{
#ifdef _DEBUG
	if(!pDepthBuffer || (pDepthBuffer && !pDepthBuffer->m_pSurface))
	{
		EE_LOGERROR("Bad parameter"); 
		return; 
	}
#endif

	m_pDepthShader->RenderDepthMap(pDepthBuffer, pView); 

	
	if(CEngine::GetInstance()->GetInput()->isKeyDown(DIK_C))
	{
		m_camera.m_View = pView->m_View;
		m_camera.m_Projection = pView->m_Projection;
	}
	else if(CEngine::GetInstance()->GetInput()->isKeyReleased(DIK_C))
	{			
		CCamera::cameraData cameraSettings = {2.0f,100.0f,150.0f}; 
		CEngine::GetInstance()->GetCamera()->initalizeCamera(cameraSettings); 
	}
}

/**************************************
 * Allows the client to specifiy the current 
 * fog parameters on the device
 **************************************/
void CD3D9Device::SetSunParameters(const SunData& sun) {
	m_SunData = sun;
	m_pLightShader->SetSkyData(sun);
}

/**************************************
 * Allows the client to specifiy the current 
 * fog parameters on the device
 **************************************/
void CD3D9Device::SetFogParamters(const FogData& fog) //(const Color3f& color, const float start, const float end, const float noise)
{
	m_pDefaultShader->SetFogParameters(fog);
}

/**************************************
 * Renders all objects via a view orrientation
 * that flips the camera about the devices
 * reflection plane
 **************************************/
void CD3D9Device::RenderReflectionBuffer() {

	#ifdef _DEBUG
	{
		LPCWSTR pMsg = L"Begin render reflection\n"; 
		D3DPERF_SetMarker(D3DCOLOR_COLORVALUE(1,0,0,1), pMsg); 
	}
	#endif


	const D3DXMATRIX origonalView = m_camera.m_View;
	const D3DXPLANE *pPlane = &m_ReflectionPlane;
	
	D3DXPLANE viewPlane,clipPlane;
	D3DXMATRIX view, proj;
	D3DXMatrixInverse(&view, NULL, &m_camera.m_View);
	D3DXMatrixTranspose(&view, &view);
	D3DXPlaneTransform(&viewPlane, &D3DXPLANE(-pPlane->a,-pPlane->b,-pPlane->c, -pPlane->d), &view);

	D3DXMatrixInverse(&proj, NULL, &m_camera.m_Projection);
	D3DXMatrixTranspose(&proj, &proj);
	D3DXPlaneTransform(&clipPlane, &viewPlane, &proj);


	D3DXMATRIX reflectMatrix;
	D3DXMatrixReflect(&reflectMatrix, pPlane);
	D3DXMatrixMultiply(&m_camera.m_View, &reflectMatrix, &m_camera.m_View);
	m_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
	UpdateViewFrustum(m_camera);

	//Updates (shared) parameters in shaders
	m_pDefaultShader->PreExecute();

	m_pD3DDevice->SetRenderTarget(0, m_pRTReflectionBuffer->m_pSurface); 
	m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER , m_BackgroundColor, 1.0f, 0); 
	m_pD3DDevice->BeginScene(); 
	{	
		if(m_RenderSky)
		{
			m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
			m_pSkyShader->Execute(); 	
			m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
		}

		m_pD3DDevice->SetClipPlane(0, (float*)&clipPlane);
		m_pD3DDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, D3DCLIPPLANE0);
		
		for(uint i = 0, j = m_Shaders.Count();  i < j;  i++)
		{
			D3DShader* pShader = m_Shaders[i];
			if(pShader != m_pSkyShader)
				pShader->Execute(RENDER_OPAQUE | RENDER_TRANSPARENT); 
		}
	}
	m_pD3DDevice->EndScene(); 


	m_camera.m_View = origonalView;
	
	m_pDefaultShader->PreExecute();
	m_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	m_pD3DDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, NULL);
}

/**************************************
 * Renders all pixels that fall behind geometry 
 * that is rendered using refraction shaders. 
 * Rendering usually occours over this data to
 * produce appropriate effects
 **************************************/
void CD3D9Device::RenderRefractionBuffer() {

	#ifdef _DEBUG
	{
		LPCWSTR pMsg = L"Begin render background refract\n"; 
		D3DPERF_SetMarker(D3DCOLOR_COLORVALUE(1,0,0,1), pMsg); 
	}
	#endif

	//TODO:
	//FogData waterFog;
	//waterFog.m_FogPlane = D3DXVECTOR3(0,1,0);
	//waterFog.m_Start = 0.0f;
	//waterFog.m_End = 15.0f;

	//FogData oldFog = m_pDefaultShader->GetFogParameters();
	//m_pDefaultShader->SetFogParameters(waterFog);

	m_pD3DDevice->SetRenderTarget(0, m_pRTRefractionBuffer->m_pSurface); 
	m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, m_BackgroundColor, 1.0f, 0); 
	m_pD3DDevice->BeginScene(); 	

	for(uint i = 0, j = m_Shaders.Count();  i < j;  i++)
		m_Shaders[i]->Execute(RENDER_REFRACTED); 
	m_pD3DDevice->EndScene(); 

//	m_pDefaultShader->SetFogParameters(oldFog);
}

void CD3D9Device::RenderPixelBuffer(void) {

	//
	//	Render non refracted geometry
	//
	//Here we render the non refracted geometry. We do this first so that it is present
	//in the frame buffer when transparent geometry is subsequently Drawn. This ensures
	//transparent geometry retains it's transparent look
	#ifdef _DEBUG
	{
		LPCWSTR pMsg = L"EEBegin render pixel geometry\n"; 
		D3DPERF_SetMarker(D3DCOLOR_COLORVALUE(1,0,0,1), pMsg); 
	}
	#endif
	m_pD3DDevice->SetRenderTarget(0, m_pRTPixelBuffer->m_pSurface); 
	m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, m_BackgroundColor, 1.0f, 0); 
	//m_pD3DDevice->SetRenderTarget(1, NULL); 
	
	m_pD3DDevice->SetRenderTarget(1, m_pRTBloomBuffer->m_pSurface); 
	m_pD3DDevice->Clear(1, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0); 
	m_pD3DDevice->SetRenderTarget(2, NULL); 
	
	m_pD3DDevice->BeginScene(); 

	if(m_RenderSky)
	{
		m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
		m_pSkyShader->Execute(); 	
		m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
	}

	for(uint i = 0, j = m_Shaders.Count();  i < j;  i++)
	{
		D3DShader* pShader = m_Shaders[i];
		if(pShader == m_pSkyShader) continue;
		pShader->Execute(RENDER_OPAQUE | RENDER_TRANSPARENT); 
	}
	
	OcclusionQueries();

	
	m_pD3DDevice->EndScene(); 
	m_pD3DDevice->SetRenderTarget(1, NULL); 

#ifdef _DEBUG
	{
		LPCWSTR pMsg = L"EEEnd render pixel geometry\n"; 
		D3DPERF_SetMarker(D3DCOLOR_COLORVALUE(1,0,0,1), pMsg); 
	}
#endif
}

void CD3D9Device::OcclusionQueries() {

	if(m_SunData.m_IsActive) {

		D3DXMATRIX sunTranslation;
		m_SunData.m_Position.x = -310;
		m_SunData.m_Position.y = 230;
		m_SunData.m_Position.z = -300;
		D3DXVECTOR3 SunPosition = m_SunData.m_Position + D3DXVECTOR3((float*)&m_camera.m_Position);

		D3DXMatrixTranslation(&sunTranslation,SunPosition.x,SunPosition.y,SunPosition.z);
		m_pD3DDevice->SetTransform( D3DTS_WORLD, &sunTranslation);

		if(SUCCEEDED(m_pOcclusionQuery->Issue(D3DISSUE_BEGIN))) {

			m_pD3DDevice->SetTransform( D3DTS_WORLD, &sunTranslation);
			//Prevents the sun sphere from actually being visible while enabling it to still alter the depth buffer and determine the
			//visibility of the sun
			m_pD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, NULL);
			m_pSunSphere->DrawSubset(0);
			m_pD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE);

			if(SUCCEEDED(m_pOcclusionQuery->Issue(D3DISSUE_END))) {
				uint blockLimit = 1000;
				DWORD pixelsDrawn;
				while(blockLimit > 0 && S_FALSE == m_pOcclusionQuery->GetData( (void*)&pixelsDrawn, sizeof(DWORD), D3DGETDATA_FLUSH )) {
					blockLimit--;
				}
				
				m_RenderLensFlare = (pixelsDrawn > 0);
			}
		}

		
			D3DXMATRIX vp;
			D3DXMatrixMultiply(&vp, &m_camera.m_View, &m_camera.m_Projection);
			D3DXVECTOR4 hSunPos;
			D3DXVec3Transform(&hSunPos, &SunPosition, &vp);

			Vector2f sunCoord;
			sunCoord.x = ((hSunPos.x / hSunPos.w) + 1.0f) * 0.5f;
			sunCoord.y = ((-hSunPos.y / hSunPos.w) + 1.0f) * 0.5f;

			Vector2f toCenter;
			toCenter.x = 0.5f - sunCoord.x;
			toCenter.y = 0.5f - sunCoord.y;

		if(m_RenderLensFlare) {


			for(uint i =0; i < 4; i++) {
				D3DImage* pFlare = m_SunData.m_pFlares[i];
				if(pFlare) {
					pFlare->SetAlpha(pFlare->m_alpha + 0.5f);
					pFlare->SetPosition(sunCoord + toCenter * ((float)i / 4.0f));
					pFlare->Draw();
				}
			}
		}
		else
		{
			for(uint i =0; i < 4; i++) {
				D3DImage* pFlare = m_SunData.m_pFlares[i];
				if(pFlare) {
					if(pFlare->m_alpha > 0.0f) {
						pFlare->SetAlpha(pFlare->m_alpha - 0.5f);
					pFlare->SetPosition(sunCoord + toCenter * ((float)i / 4.0f));
						pFlare->Draw();
					}
				}
			}
		}
	}
}

void CD3D9Device::PreformRendering(void)
{	
	//TODO: messy, fixme
	for(uint i = 0, j = m_PointspriteInstances.Count(); i < j; i++)
	{
		m_PointspriteInstances[i]->getShader()->AddRenderQue(m_PointspriteInstances[i], Matrix4f::identity());
	}

	m_pDepthShader->PreExecute(); 
	m_pLightShader->PreExecute(); 
	m_pLightShader->Execute();

	for(uint i = 0, j = m_Shaders.Count();  i < j;  i++)
		m_Shaders[i]->PreExecute();

	SetTexture(3, m_pWhiteTexture);
	RenderReflectionBuffer();
	RenderRefractionBuffer();

	UpdateViewFrustum(m_camera);
	//Prevents new meshs from being included in the depth map
	//and enables it to be rendered from arbitrary orrientations
 

	m_pLightShader->PostExecute(); 

	m_pLightTexture = m_pLightShader->GetLightBuffer();
	SetTexture(3,m_pLightTexture); 
	

	RenderPixelBuffer();


#ifdef _DEBUG
	RenderDebug();
#endif


	for(uint i = 0, j = m_Shaders.Count();  i < j;  i++)
		m_Shaders[i]->PostExecute(); 
	
	//Shaders have been used twice to render opaque and transparent
	//geometry. The render instance ques have remained the same for 
	//both rendering situations. We need to clear these ques for the
	//subsequent frame
	for(unsigned int i = 0, j = m_Shaders.Count();  i < j;  i++)
		m_Shaders[i]->FlushRenderList(); 

	for(uint i = 0, j = m_PointspriteInstances.Count();  i < j;  i++)
		m_PointspriteInstances[i]->Flush(); 

	//Allows new meshs to be present in the depth map
	//however it's data can no longer be accessed
	m_pDepthShader->PostExecute(); 
	
	//TODO: check if the textures of RT's can be released per frame

#ifdef _DEBUG
	{
		LPCWSTR pMsg = L"Begin post process\n"; 
		D3DPERF_SetMarker(D3DCOLOR_COLORVALUE(1,0,0,1), pMsg); 
	}
#endif 
	
	LPDIRECT3DTEXTURE9 pFinalBuffer = NULL;
	switch(m_RenderMode)
	{
		default:case DEFAULT:
			pFinalBuffer = m_pRTPixelBuffer->m_pTargetTexture;
			break; 
		case LIGHTING:
			pFinalBuffer = m_pLightTexture; 
			break; 
		case SURFACE_DEPTH:
			pFinalBuffer = m_pLightShader->m_RTDepth->m_pTargetTexture;
			break; 
		case SURFACE_NORMAL:
			pFinalBuffer = m_pLightShader->m_RTNormal->m_pTargetTexture;
			break; 
		case VELOCITY:
			pFinalBuffer = m_pLightShader->m_RTVelocity->m_pTargetTexture;
			break; 
		case REFLECTION:
			pFinalBuffer = m_pRTReflectionBuffer->m_pTargetTexture;
			break; 
	}

	m_pD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); 
	m_pD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); 
	SetTexture(1,m_pLightShader->GetVelocityBuffer()); 	
	SetTexture(2,m_pRTBloomBuffer->m_pTargetTexture);
	m_pScreenShader->Execute(m_pBackBufferSurface, pFinalBuffer);

	m_pSpriteBatch->Begin(D3DXSPRITE_ALPHABLEND);
	RenderScreenText();
	m_pSpriteBatch->End();

	m_pD3DDevice->Present(NULL, NULL, NULL, NULL); 
	
	m_RenderReflections = false;
	m_RenderSky = false;
}
	
void CD3D9Device::RenderScreenItems() {

	m_pD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); 
	m_pD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); 
	if(SUCCEEDED(m_pSpriteBatch->Begin(D3DXSPRITE_ALPHABLEND )))
	{
		for(uint i = 0;  i < m_Sprites.Count();  i++)
		{
			D3DImage* pSprite = m_Sprites[i]; 

			if(pSprite->m_Draw)
			{
				float x = pSprite->screenPosition.x * m_deviceSettings.screenWidth;
				float y = pSprite->screenPosition.y * m_deviceSettings.screenHeight;
				D3DXMATRIX t;
				D3DXMatrixTranslation(&t,x,y,0);
				t = pSprite->m_scale * t;
				m_pSpriteBatch->SetTransform(&t);
									
				D3DTexture2D* pTexture = pSprite->pTexutre; 
				float z = 0; 

				m_pSpriteBatch->Draw((LPDIRECT3DTEXTURE9)pTexture->DataD3D(), NULL, 
					&D3DXVECTOR3((pTexture->textureInfo.Width / 2.0f),(pTexture->textureInfo.Height / 2.0f),0),
					NULL,
					D3DCOLOR_COLORVALUE(1,1,1,pSprite->m_alpha) ); 
				pSprite->m_Draw = false; 
			}
		}
		m_pSpriteBatch->End(); 
	}
}

LPDIRECT3DBASETEXTURE9 CD3D9Device::GetTexture(cuint unit) {

#ifdef _DEBUG
	if(unit > DEVICE_TEXTURE_UNITS - 1)
	{
		EE_LOGERROR("Only 8 texture units supported")
		return NULL;
	}
#endif

	return m_deviceTextures[unit];
}

void CD3D9Device::SetTexture(const unsigned int unit,Texture* const pTexture)
{
#ifdef _DEBUG
	if(unit > DEVICE_TEXTURE_UNITS - 1)
	{
		EE_LOGERROR("Only 8 texture units supported")
		return;
	}
#endif

	if(ISNULL(pTexture))
	{
		m_deviceTextures[unit] = m_pWhiteTexture->DataD3D();
	}
	else
	{
		LPDIRECT3DBASETEXTURE9 pData = pTexture->DataD3D();
		if(pData != m_deviceTextures[unit])
		{
			m_deviceTextures[unit] = pData;
			m_pD3DDevice->SetTexture(unit, pData);
		}
	}
}


void CD3D9Device::SetTexture(const unsigned int unit,LPDIRECT3DBASETEXTURE9 pTexture)
{
#ifdef _DEBUG
	if(unit > DEVICE_TEXTURE_UNITS - 1)
	{
		EE_LOGERROR("Only 8 texture units supported")
		return;
	}
#endif

	if(ISNULL(pTexture))
	{
		m_deviceTextures[unit] = m_pWhiteTexture->DataD3D();
	}
	else
	{
		if(pTexture != m_deviceTextures[unit])
		{
			m_deviceTextures[unit] = pTexture;
			m_pD3DDevice->SetTexture(unit, pTexture);
		}
	}
}